// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/mouse_wheel_event_queue.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
#include "content/common/input/synthetic_web_input_event_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebInputEvent.h"
#include "ui/events/base_event_utils.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseWheelEvent;

namespace content {
namespace {

    const float kWheelScrollX = 10;
    const float kWheelScrollY = 12;
    const float kWheelScrollGlobalX = 50;
    const float kWheelScrollGlobalY = 72;

#define EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event)                  \
    EXPECT_EQ(WebInputEvent::GestureScrollBegin, event->type()); \
    EXPECT_EQ(kWheelScrollX, event->x);                          \
    EXPECT_EQ(kWheelScrollY, event->y);                          \
    EXPECT_EQ(kWheelScrollGlobalX, event->globalX);              \
    EXPECT_EQ(kWheelScrollGlobalY, event->globalY);              \
    EXPECT_EQ(scroll_units, event->data.scrollBegin.deltaHintUnits);

#define EXPECT_GESTURE_SCROLL_BEGIN(event)           \
    EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);         \
    EXPECT_FALSE(event->data.scrollBegin.synthetic); \
    EXPECT_EQ(WebGestureEvent::UnknownMomentumPhase, \
        event->data.scrollBegin.inertialPhase);

#define EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(event) \
    EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);          \
    EXPECT_FALSE(event->data.scrollBegin.synthetic);  \
    EXPECT_EQ(WebGestureEvent::NonMomentumPhase,      \
        event->data.scrollBegin.inertialPhase);

#define EXPECT_SYNTHETIC_GESTURE_SCROLL_BEGIN(event) \
    EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);         \
    EXPECT_TRUE(event->data.scrollBegin.synthetic);  \
    EXPECT_EQ(WebGestureEvent::NonMomentumPhase,     \
        event->data.scrollBegin.inertialPhase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_BEGIN(event)  \
    EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);         \
    EXPECT_FALSE(event->data.scrollBegin.synthetic); \
    EXPECT_EQ(WebGestureEvent::MomentumPhase,        \
        event->data.scrollBegin.inertialPhase);

#define EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_BEGIN(event) \
    EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                  \
    EXPECT_TRUE(event->data.scrollBegin.synthetic);           \
    EXPECT_EQ(WebGestureEvent::MomentumPhase,                 \
        event->data.scrollBegin.inertialPhase);

#define EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event)                  \
    EXPECT_EQ(WebInputEvent::GestureScrollUpdate, event->type()); \
    EXPECT_EQ(scroll_units, event->data.scrollUpdate.deltaUnits); \
    EXPECT_EQ(kWheelScrollX, event->x);                           \
    EXPECT_EQ(kWheelScrollY, event->y);                           \
    EXPECT_EQ(kWheelScrollGlobalX, event->globalX);               \
    EXPECT_EQ(kWheelScrollGlobalY, event->globalY);

#define EXPECT_GESTURE_SCROLL_UPDATE(event)          \
    EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);        \
    EXPECT_EQ(WebGestureEvent::UnknownMomentumPhase, \
        event->data.scrollUpdate.inertialPhase);

#define EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(event) \
    EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);          \
    EXPECT_EQ(WebGestureEvent::NonMomentumPhase,       \
        event->data.scrollUpdate.inertialPhase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(event) \
    EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);        \
    EXPECT_EQ(WebGestureEvent::MomentumPhase,        \
        event->data.scrollUpdate.inertialPhase);

#define EXPECT_GESTURE_SCROLL_END_IMPL(event)                  \
    EXPECT_EQ(WebInputEvent::GestureScrollEnd, event->type()); \
    EXPECT_EQ(scroll_units, event->data.scrollEnd.deltaUnits); \
    EXPECT_EQ(kWheelScrollX, event->x);                        \
    EXPECT_EQ(kWheelScrollY, event->y);                        \
    EXPECT_EQ(kWheelScrollGlobalX, event->globalX);            \
    EXPECT_EQ(kWheelScrollGlobalY, event->globalY);

#define EXPECT_GESTURE_SCROLL_END(event)             \
    EXPECT_GESTURE_SCROLL_END_IMPL(event);           \
    EXPECT_FALSE(event->data.scrollEnd.synthetic);   \
    EXPECT_EQ(WebGestureEvent::UnknownMomentumPhase, \
        event->data.scrollEnd.inertialPhase);

#define EXPECT_GESTURE_SCROLL_END_WITH_PHASE(event) \
    EXPECT_GESTURE_SCROLL_END_IMPL(event);          \
    EXPECT_FALSE(event->data.scrollEnd.synthetic);  \
    EXPECT_EQ(WebGestureEvent::NonMomentumPhase,    \
        event->data.scrollEnd.inertialPhase);

#define EXPECT_SYNTHETIC_GESTURE_SCROLL_END(event) \
    EXPECT_GESTURE_SCROLL_END_IMPL(event);         \
    EXPECT_TRUE(event->data.scrollEnd.synthetic);  \
    EXPECT_EQ(WebGestureEvent::NonMomentumPhase,   \
        event->data.scrollEnd.inertialPhase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_END(event)  \
    EXPECT_GESTURE_SCROLL_END_IMPL(event);         \
    EXPECT_FALSE(event->data.scrollEnd.synthetic); \
    EXPECT_EQ(WebGestureEvent::MomentumPhase,      \
        event->data.scrollEnd.inertialPhase);

#define EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_END(event) \
    EXPECT_GESTURE_SCROLL_END_IMPL(event);                  \
    EXPECT_TRUE(event->data.scrollEnd.synthetic);           \
    EXPECT_EQ(WebGestureEvent::MomentumPhase,               \
        event->data.scrollEnd.inertialPhase);

#define EXPECT_MOUSE_WHEEL(event) \
    EXPECT_EQ(WebInputEvent::MouseWheel, event->type());

} // namespace

class MouseWheelEventQueueTest : public testing::TestWithParam<bool>,
                                 public MouseWheelEventQueueClient {
public:
    MouseWheelEventQueueTest()
        : acked_event_count_(0)
        , last_acked_event_state_(INPUT_EVENT_ACK_STATE_UNKNOWN)
    {
        scroll_latching_enabled_ = GetParam();
        queue_.reset(new MouseWheelEventQueue(this, scroll_latching_enabled_));
        scroll_end_timeout_ms_ = scroll_latching_enabled_ ? 100 : 0;
    }

    ~MouseWheelEventQueueTest() override { }

    // MouseWheelEventQueueClient
    void SendMouseWheelEventImmediately(
        const MouseWheelEventWithLatencyInfo& event) override
    {
        WebMouseWheelEvent* cloned_event = new WebMouseWheelEvent();
        std::unique_ptr<WebInputEvent> cloned_event_holder(cloned_event);
        *cloned_event = event.event;
        sent_events_.push_back(std::move(cloned_event_holder));
    }

    void ForwardGestureEventWithLatencyInfo(
        const blink::WebGestureEvent& event,
        const ui::LatencyInfo& latency_info) override
    {
        WebGestureEvent* cloned_event = new WebGestureEvent();
        std::unique_ptr<WebInputEvent> cloned_event_holder(cloned_event);
        *cloned_event = event;
        sent_events_.push_back(std::move(cloned_event_holder));
    }

    void OnMouseWheelEventAck(const MouseWheelEventWithLatencyInfo& event,
        InputEventAckState ack_result) override
    {
        ++acked_event_count_;
        last_acked_event_ = event.event;
        last_acked_event_state_ = ack_result;
    }

    base::TimeDelta DefaultScrollEndTimeoutDelay()
    {
        return base::TimeDelta::FromMilliseconds(scroll_end_timeout_ms_);
    }

    bool scroll_latching_enabled() { return scroll_latching_enabled_; }

protected:
    size_t queued_event_count() const { return queue_->queued_size(); }

    bool event_in_flight() const { return queue_->event_in_flight(); }

    std::vector<std::unique_ptr<WebInputEvent>>& all_sent_events()
    {
        return sent_events_;
    }

    const std::unique_ptr<WebInputEvent>& sent_input_event(size_t index)
    {
        return sent_events_[index];
    }
    const WebGestureEvent* sent_gesture_event(size_t index)
    {
        return static_cast<WebGestureEvent*>(sent_events_[index].get());
    }

    const WebMouseWheelEvent& acked_event() const { return last_acked_event_; }

    size_t GetAndResetSentEventCount()
    {
        size_t count = sent_events_.size();
        sent_events_.clear();
        return count;
    }

    size_t GetAndResetAckedEventCount()
    {
        size_t count = acked_event_count_;
        acked_event_count_ = 0;
        return count;
    }

    void SendMouseWheelEventAck(InputEventAckState ack_result)
    {
        queue_->ProcessMouseWheelAck(ack_result, ui::LatencyInfo());
    }

    void SendMouseWheel(float x,
        float y,
        float global_x,
        float global_y,
        float dX,
        float dY,
        int modifiers,
        bool high_precision,
        WebInputEvent::RailsMode rails_mode)
    {
        WebMouseWheelEvent event = SyntheticWebMouseWheelEventBuilder::Build(
            x, y, global_x, global_y, dX, dY, modifiers, high_precision);
        event.railsMode = rails_mode;
        queue_->QueueEvent(MouseWheelEventWithLatencyInfo(event));
    }

    void SendMouseWheel(float x,
        float y,
        float global_x,
        float global_y,
        float dX,
        float dY,
        int modifiers,
        bool high_precision)
    {
        SendMouseWheel(x, y, global_x, global_y, dX, dY, modifiers, high_precision,
            WebInputEvent::RailsModeFree);
    }
    void SendMouseWheelWithPhase(
        float x,
        float y,
        float global_x,
        float global_y,
        float dX,
        float dY,
        int modifiers,
        bool high_precision,
        blink::WebMouseWheelEvent::Phase phase,
        blink::WebMouseWheelEvent::Phase momentum_phase)
    {
        WebMouseWheelEvent event = SyntheticWebMouseWheelEventBuilder::Build(
            x, y, global_x, global_y, dX, dY, modifiers, high_precision);
        event.phase = phase;
        event.momentumPhase = momentum_phase;
        queue_->QueueEvent(MouseWheelEventWithLatencyInfo(event));
    }

    void SendGestureEvent(WebInputEvent::Type type)
    {
        WebGestureEvent event(type, WebInputEvent::NoModifiers,
            ui::EventTimeStampToSeconds(ui::EventTimeForNow()));
        event.sourceDevice = blink::WebGestureDeviceTouchscreen;
        queue_->OnGestureScrollEvent(
            GestureEventWithLatencyInfo(event, ui::LatencyInfo()));
    }

    static void RunTasksAndWait(base::TimeDelta delay)
    {
        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
            FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), delay);
        base::RunLoop().Run();
    }

    void GestureSendingTest(bool high_precision)
    {
        const WebGestureEvent::ScrollUnits scroll_units = high_precision ? WebGestureEvent::PrecisePixels
                                                                         : WebGestureEvent::Pixels;
        SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 1, 1, 0, high_precision);
        EXPECT_EQ(0U, queued_event_count());
        EXPECT_TRUE(event_in_flight());
        EXPECT_EQ(1U, GetAndResetSentEventCount());

        // The second mouse wheel should not be sent since one is already in
        // queue.
        SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 5, 5, 0, high_precision);
        EXPECT_EQ(1U, queued_event_count());
        EXPECT_TRUE(event_in_flight());
        EXPECT_EQ(0U, GetAndResetSentEventCount());

        // Receive an ACK for the mouse wheel event and release the next
        // mouse wheel event.
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        EXPECT_EQ(0U, queued_event_count());
        EXPECT_TRUE(event_in_flight());
        EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
        EXPECT_EQ(1U, GetAndResetAckedEventCount());
        EXPECT_EQ(3U, all_sent_events().size());
        EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
        EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
        EXPECT_MOUSE_WHEEL(sent_input_event(2));
        EXPECT_EQ(3U, GetAndResetSentEventCount());

        RunTasksAndWait(DefaultScrollEndTimeoutDelay() * 2);
        EXPECT_EQ(1U, all_sent_events().size());
        EXPECT_GESTURE_SCROLL_END(sent_gesture_event(0));
    }

    void PhaseGestureSendingTest(bool high_precision)
    {
        const WebGestureEvent::ScrollUnits scroll_units = high_precision ? WebGestureEvent::PrecisePixels
                                                                         : WebGestureEvent::Pixels;

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 1, 1, 0, high_precision,
            WebMouseWheelEvent::PhaseBegan,
            WebMouseWheelEvent::PhaseNone);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            EXPECT_EQ(2U, all_sent_events().size());
            EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
            EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
            EXPECT_EQ(2U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(3U, all_sent_events().size());
            EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
            EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
            EXPECT_SYNTHETIC_GESTURE_SCROLL_END(sent_gesture_event(2));
            EXPECT_EQ(3U, GetAndResetSentEventCount());
        }

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 5, 5, 0, high_precision,
            WebMouseWheelEvent::PhaseChanged,
            WebMouseWheelEvent::PhaseNone);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            EXPECT_EQ(1U, all_sent_events().size());
            EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(0));
            EXPECT_EQ(1U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(3U, all_sent_events().size());
            EXPECT_SYNTHETIC_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
            EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
            EXPECT_SYNTHETIC_GESTURE_SCROLL_END(sent_gesture_event(2));
            EXPECT_EQ(3U, GetAndResetSentEventCount());
        }

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 0, 0, 0, high_precision,
            WebMouseWheelEvent::PhaseEnded,
            WebMouseWheelEvent::PhaseNone);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            // ScrollEnd is not sent right away, a timer starts instead,
            // to see if a wheel event with momentumPhase arrives or not.
            EXPECT_EQ(0U, all_sent_events().size());
            EXPECT_EQ(0U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(2U, all_sent_events().size());
            EXPECT_SYNTHETIC_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
            EXPECT_GESTURE_SCROLL_END_WITH_PHASE(sent_gesture_event(1));
            EXPECT_EQ(2U, GetAndResetSentEventCount());
        }

        // Send a double phase end; OSX does it consistently.
        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 0, 0, 0, high_precision,
            WebMouseWheelEvent::PhaseEnded,
            WebMouseWheelEvent::PhaseNone);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        EXPECT_EQ(0U, all_sent_events().size());
        EXPECT_EQ(0U, GetAndResetSentEventCount());

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 5, 5, 0, high_precision,
            WebMouseWheelEvent::PhaseNone,
            WebMouseWheelEvent::PhaseBegan);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            // A fling has started, no ScrollEnd/ScrollBegin is sent.
            EXPECT_EQ(1U, all_sent_events().size());
            EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(0));
            EXPECT_EQ(1U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(3U, all_sent_events().size());
            EXPECT_INERTIAL_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
            EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
            EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(2));
            EXPECT_EQ(3U, GetAndResetSentEventCount());
        }

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 5, 5, 0, high_precision,
            WebMouseWheelEvent::PhaseNone,
            WebMouseWheelEvent::PhaseChanged);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            EXPECT_EQ(1U, all_sent_events().size());
            EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(0));
            EXPECT_EQ(1U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(3U, all_sent_events().size());
            EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
            EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
            EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(2));
            EXPECT_EQ(3U, GetAndResetSentEventCount());
        }

        SendMouseWheelWithPhase(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
            kWheelScrollGlobalY, 0, 0, 0, high_precision,
            WebMouseWheelEvent::PhaseNone,
            WebMouseWheelEvent::PhaseEnded);
        EXPECT_EQ(1U, GetAndResetSentEventCount());
        SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
        if (scroll_latching_enabled_) {
            // MomentumPhase is ended, the scroll is done, and GSE is sent
            // immediately.
            EXPECT_EQ(1U, all_sent_events().size());
            EXPECT_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(0));
            EXPECT_EQ(1U, GetAndResetSentEventCount());
        } else {
            EXPECT_EQ(2U, all_sent_events().size());
            EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
            EXPECT_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(1));
            EXPECT_EQ(2U, GetAndResetSentEventCount());
        }
    }

    std::unique_ptr<MouseWheelEventQueue> queue_;
    std::vector<std::unique_ptr<WebInputEvent>> sent_events_;
    size_t acked_event_count_;
    InputEventAckState last_acked_event_state_;
    base::MessageLoopForUI message_loop_;
    WebMouseWheelEvent last_acked_event_;
    int64_t scroll_end_timeout_ms_;
    bool scroll_latching_enabled_;
};

// Tests that mouse wheel events are queued properly.
TEST_P(MouseWheelEventQueueTest, Basic)
{
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // The second mouse wheel should not be sent since one is already in queue.
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 5, 5, 0, false);
    EXPECT_EQ(1U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(0U, GetAndResetSentEventCount());

    // Receive an ACK for the first mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());

    // Receive an ACK for the second mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(0U, GetAndResetSentEventCount());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
}

TEST_P(MouseWheelEventQueueTest, GestureSending)
{
    GestureSendingTest(false);
}

TEST_P(MouseWheelEventQueueTest, GestureSendingPrecisePixels)
{
    GestureSendingTest(true);
}

TEST_P(MouseWheelEventQueueTest, GestureSendingWithPhaseInformation)
{
    PhaseGestureSendingTest(false);
}

TEST_P(MouseWheelEventQueueTest,
    GestureSendingWithPhaseInformationPrecisePixels)
{
    PhaseGestureSendingTest(true);
}

TEST_P(MouseWheelEventQueueTest, GestureSendingInterrupted)
{
    const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::Pixels;

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
    EXPECT_EQ(2U, GetAndResetSentEventCount());

    // Ensure that a gesture scroll begin terminates the current scroll event.
    SendGestureEvent(WebInputEvent::GestureScrollBegin);
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_END(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // New mouse wheel events won't cause gestures because a scroll
    // is already in progress by another device.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(0U, all_sent_events().size());

    SendGestureEvent(WebInputEvent::GestureScrollEnd);
    EXPECT_EQ(0U, all_sent_events().size());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
    EXPECT_EQ(2U, GetAndResetSentEventCount());
}

TEST_P(MouseWheelEventQueueTest, GestureRailScrolling)
{
    const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::Pixels;

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false,
        WebInputEvent::RailsModeHorizontal);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
    EXPECT_EQ(1U, sent_gesture_event(1)->data.scrollUpdate.deltaX);
    EXPECT_EQ(0U, sent_gesture_event(1)->data.scrollUpdate.deltaY);
    EXPECT_EQ(2U, GetAndResetSentEventCount());

    RunTasksAndWait(DefaultScrollEndTimeoutDelay() * 2);
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_END(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false,
        WebInputEvent::RailsModeVertical);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
    EXPECT_EQ(0U, sent_gesture_event(1)->data.scrollUpdate.deltaX);
    EXPECT_EQ(1U, sent_gesture_event(1)->data.scrollUpdate.deltaY);
    EXPECT_EQ(2U, GetAndResetSentEventCount());
}

TEST_P(MouseWheelEventQueueTest, WheelScrollLatching)
{
    const WebGestureEvent::ScrollUnits scroll_units = WebGestureEvent::Pixels;
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false,
        WebInputEvent::RailsModeVertical);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(1));
    EXPECT_EQ(0U, sent_gesture_event(1)->data.scrollUpdate.deltaX);
    EXPECT_EQ(1U, sent_gesture_event(1)->data.scrollUpdate.deltaY);
    EXPECT_EQ(2U, GetAndResetSentEventCount());

    RunTasksAndWait(base::TimeDelta::FromMilliseconds(1));
    if (!scroll_latching_enabled_) {
        EXPECT_GESTURE_SCROLL_END(sent_gesture_event(0));
        EXPECT_EQ(1U, GetAndResetSentEventCount());
    } else {
        EXPECT_EQ(0U, GetAndResetSentEventCount());
    }
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
        kWheelScrollGlobalY, 1, 1, 0, false,
        WebInputEvent::RailsModeVertical);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event.
    SendMouseWheelEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_FALSE(event_in_flight());
    EXPECT_EQ(WebInputEvent::MouseWheel, acked_event().type());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());

    int updateEventIndex = -1;
    if (!scroll_latching_enabled_) {
        EXPECT_EQ(2U, all_sent_events().size());
        EXPECT_GESTURE_SCROLL_BEGIN(sent_gesture_event(0));
        updateEventIndex = 1;
    } else {
        // Scroll latching: no new scroll begin expected.
        EXPECT_EQ(1U, all_sent_events().size());
        updateEventIndex = 0;
    }
    EXPECT_GE(updateEventIndex, 0);
    EXPECT_GESTURE_SCROLL_UPDATE(sent_gesture_event(updateEventIndex));
    EXPECT_EQ(0U, sent_gesture_event(updateEventIndex)->data.scrollUpdate.deltaX);
    EXPECT_EQ(1U, sent_gesture_event(updateEventIndex)->data.scrollUpdate.deltaY);
    EXPECT_EQ(static_cast<size_t>(updateEventIndex + 1),
        GetAndResetSentEventCount());
}

INSTANTIATE_TEST_CASE_P(MouseWheelEventQueueTests,
    MouseWheelEventQueueTest,
    testing::Bool());

} // namespace content
